package utils;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.util.ArrayList;

import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import java.util.Random;

import net.didion.jwnl.JWNL;
import net.didion.jwnl.JWNLException;
import net.didion.jwnl.data.IndexWord;
import net.didion.jwnl.data.POS;
import net.didion.jwnl.data.PointerUtils;
import net.didion.jwnl.data.Synset;
import net.didion.jwnl.data.Word;
import net.didion.jwnl.data.list.PointerTargetNode;
import net.didion.jwnl.data.list.PointerTargetNodeList;

import net.didion.jwnl.dictionary.Dictionary;

/**
 * A wrapper which provides easy access to selected WordNet operations.
 * 
 * <p>Used to provide random words and word lookup for the IJP ChatBot
 * assignment. Lookup is provided through the JWNL dictionary operations.</p>
 *   
 * <p>Note: Generation of random words is accomplished through use of additional data
 * structures containing nouns, verbs and adjectives. These additional structures
 * speed up the process of retrieving a random word, as the JWNL library has
 * no suitable random access methods.</p>
 * 
 * <p>This code need not be changed for the assignement. It may be useful for
 * msc summer projects.</p> 
 * 
 * @author Judy Robertson
 */
public class WordNetWrapper {

    /**
     * The WordNet dictionary structure
     */
    private Dictionary d;
    
    /**
     * A list of the verbs in WordNet
     */
    private List verbList = new ArrayList();
    
    /**
     * A list of the nouns in WordNet
     */
    private List nounList = new ArrayList();
    
    /**
     * A list of the adjectives in WorNet
     */
    private List adjectiveList = new ArrayList();

    /**
     * A Pseudo-random generator used to find words at random
     */
    private Random randomGenerator;

    /**
     * Creates a new WordNetWrapper, initialises the WordNet
     * dictionary and reads in the pre-generated additional nouns, verbs and adjectives
     * lists from disc.
     *
     */
    public WordNetWrapper() {
        try {
            FileInputStream fi =
                new FileInputStream(System.getProperty("user.dir") +
                		File.separator + "jwnl" + File.separator +  "file_properties.xml");
            JWNL.initialize(fi);
            d = Dictionary.getInstance();
            readInNouns();
            readInAdjectives();
            readInVerbs();
            randomGenerator = new Random();
        } catch (IOException e) {
            System.err.println("An IO error when loading wordnet");
            //e.printStackTrace();
        } catch (JWNLException err) {
            System.err.println("A JWNL exception occured during intialisation");
            err.printStackTrace();
        }

    }

    /**
     * Reads in the pre-generated list of adjectives from disc into a data 
     * structure which can be accessed in constant time to find random adjectives
     *
     */
    private void readInAdjectives() {
        File f =
            new File(System.getProperty("user.home") + File.separator + "wordnet" + File.separator + "data" + 
            File.separator + "adjective" + ".dat");

        try {

            FileInputStream fin = new FileInputStream(f);

            ObjectInputStream istrm = new ObjectInputStream(fin);

            adjectiveList = (ArrayList) istrm.readObject();

        } catch (IOException e) {

            System.out.println("Trouble processing adjective file");

        } catch (ClassNotFoundException c) {

            System.err.println("Can't find class when trying to read serialised " 
            + "adjective words");

        }

    }

    /**
     * Reads in the pre-generated list of nouns from disc into a data structure
     * which can be accessed in constant time to find random nouns
     *
     */
    private void readInNouns() {
        File f = new File(System.getProperty("user.home") + File.separator + "wordnet" + File.separator + 
        "data" + File.separator + "noun" + ".dat");

        try {

            FileInputStream fin = new FileInputStream(f);

            ObjectInputStream istrm = new ObjectInputStream(fin);

            nounList = (ArrayList) istrm.readObject();

        } catch (IOException e) {

            System.out.println("Trouble processing noun file");

        } catch (ClassNotFoundException c) {

            System.err.println("Can't find class when trying to read serialised " 
            + "noun words");

        }

    }

    /**
    * Reads in the pre-generated list of verbs from disc into a data structure
    * which can be accessed in constant time to find random verbs
    *
    */
    private void readInVerbs() {
        File f = new File(System.getProperty("user.home") + File.separator + "wordnet" + File.separator + 
            "data" + File.separator + "verb" + ".dat");

        try {

            FileInputStream fin = new FileInputStream(f);

            ObjectInputStream istrm = new ObjectInputStream(fin);

            verbList = (ArrayList) istrm.readObject();

        } catch (IOException e) {

            System.out.println("Trouble processing verb file");

        } catch (ClassNotFoundException c) {

            System.err.println("Can't find class when trying to read serialised " 
            + "verb words");

        }

    }

    /**
     * Retrieve a verb at random from WordNet
     * 
     * @return A string containing a randomly generated verb
     */
    public String getVerbAtRandom() {
        String verb = "";

        int target = randomGenerator.nextInt(verbList.size());
        IndexWord word = (IndexWord) verbList.get(target);
        verb = word.getLemma();
        return verb;
    }

       /**
       * Retrieve an adjective at random from WordNet
       * 
       * @return A string containing a randomly generated adjective
       */
    public String getAdjectiveAtRandom() {
        String adj = "";

        int target = randomGenerator.nextInt(adjectiveList.size());

        IndexWord word = (IndexWord) adjectiveList.get(target);
        adj = word.getLemma();
        return adj;

    }

       /**
       * Retrieve a noun at random from WordNet
       * 
       * @return A string containing a randomly generated noun
       */
    public String getNounAtRandom() {
        String noun = "";

        int target = randomGenerator.nextInt(nounList.size());
        IndexWord word = (IndexWord) nounList.get(target);
        noun = word.getLemma();
        return noun;

    }
    
    /**
     * Find the synonyms of the specified word for the specified part of speech
     * 
     * @param pos The part of speech category for the word
     * 
     * @param lookupWord The word for which synonyms are required
     * 
     * @return A list containing strings representing synonyms of the specified 
     * word with the specified part of speech
     */
    public List getSynonyms(POS pos, String lookupWord) {

        Word[] words;
        Set synonymSet = new HashSet();
        try {
            IndexWord word = d.lookupIndexWord(pos, lookupWord);
            if (word != null) {
                //iterate over each sense of this word
                for (int j = 1; j < word.getSenseCount() + 1; j++) {

                    Synset syn = word.getSense(j);

                    //now add all the associated words from this synset to the 
                    //synonyms set
                    for (int k = 0; k < syn.getWords().length; k++) {
                        synonymSet.add(syn.getWords()[k].getLemma());

                    }
                    PointerTargetNodeList synonyms = PointerUtils.getSynonyms(syn);

                    if (synonyms != null) {
                        //if this sense of the word has synonyms, iterate over them
                        Iterator i = synonyms.iterator();
                        while (i.hasNext()) {

                            PointerTargetNode targetNode = (PointerTargetNode) i.next();
                            //if this synonym is just a string (lexical) 
                            //add it to the synonym list
                            if (targetNode.isLexical()) {
                                synonymSet.add(targetNode.getWord().getLemma());

                            } else {
                                // It's a synset rather than a word, 
                                //so retrieve all the words and add them to the 
                                //synonym list
                                words = targetNode.getSynset().getWords();
                                for (int t = 0; t < words.length; t++) {
                                    synonymSet.add(words[t].getLemma());

                                }

                            }

                        }

                    }
                }

            }

        } catch (JWNLException err) {
            err.printStackTrace();
        }
        return new ArrayList(synonymSet);
    }

    /**
     * Checks whether the specified word is a noun or not
     * 
     * @param s The word to be checked
     * 
     * @return True if the specified string is a noun in WordNet, false otherwise
     */
    public boolean isNoun(String s) {
        try {
            if (d.lookupIndexWord(POS.NOUN, s) != null){
                return true;
            }
        } catch (JWNLException e) {

            e.printStackTrace();
        }
        return false;
    }
    
    /**
    * Checks whether the specified word is a verb or not
    *
    *  @param s The word to be checked
    * 
    * @return True if the specified string is a verb in WordNet, false otherwise
    */
   
    public boolean isVerb(String s) {
        try {
            if (d.lookupIndexWord(POS.VERB, s) != null){
                return true;
            }
        } catch (JWNLException e) {

            e.printStackTrace();
        }
        return false;
    }
    
    /**
     * Checks whether the specified word is an adjective or not
     * 
     * @param s The word to be checked
     * 
     * @return True if the specified string is an adjective in WordNet, 
     * false otherwise
     */
    
    public boolean isAdjective(String s) {
        try {
            if (d.lookupIndexWord(POS.ADJECTIVE, s) != null){
                return true;
            }
        } catch (JWNLException e) {

            e.printStackTrace();
        }
        return false;
    }

    /**
     * Retrieve a set of words representing the hypernyms for every sense of the
     *  specified word and part of speech.
     * A hypernym is a superordinate word -- (a word that is more generic than 
     * a given word)
     * <p> Note: Adjectives don't have hypernyms in WordNet; this method with an
     *  adjective part of speech will return an empty list </p>
     * 
     * @param pos The part of speech for the word
     * 
     * @param e The word to be looked up 
     * 
     * @return A list containing all the direct hypernyms of all senses of the 
     * specified word, assuming the given POS.
     */
    
    public List getHypernyms(POS pos, String e) {
        Set hypernymSet = new HashSet();
        if (pos == POS.ADJECTIVE)
            System.err.println("Sorry, adjectives don't have hypernyms, " +                "will return empty list");
        else {
            Word[] words;

            try {
                IndexWord word = d.lookupIndexWord(pos, e);
                if (word != null) {
                    //iterate over each sense of this word
                    for (int j = 1; j < word.getSenseCount() + 1; j++) {

                        Synset syn = word.getSense(j);

                        PointerTargetNodeList hypernyms = PointerUtils.getDirectHypernyms(syn);

                        if (hypernyms != null) {
                            //if this sense of the word has hypernyms, 
                            //iterate over them
                            Iterator i = hypernyms.iterator();
                            while (i.hasNext()) {

                                PointerTargetNode targetNode = (PointerTargetNode) i.next();
                                //if this hypernym is just a string (lexical) 
                                //add it to the hypernym list
                                if (targetNode.isLexical()) {
                                    hypernymSet.add(targetNode.getWord().getLemma());

                                } else {
                                    // It's a synset rather than a word, 
                                    //so retrieve all the words and add them 
                                    //to the hypernym list
                                    words = targetNode.getSynset().getWords();
                                    for (int t = 0; t < words.length; t++) {
                                        hypernymSet.add(words[t].getLemma());

                                    }

                                }

                            }

                        }
                    }

                }

            } catch (JWNLException err) {
                err.printStackTrace();
            }
        }
        return new ArrayList(hypernymSet);
    }

    /**
     * Retrieve a list of words representing the meronyms for every sense of the
     * specified word and POS.
     * Meronym - The name of a constituent part of, the substance of, 
     * or a member of something. X is a meronym of Y if X is a part of Y . 
     * <p> Note: Adjectives and verbs don't have hypernyms in WordNet; 
     * this method with an adjective or verb part of speech 
     * will return an empty list </p>
     * 
     * @param pos The part of speech for the word
     * 
     * @param e The word to be looked up 
     * 
     * @return A list containing all the direct meronyms of all senses of the 
     * specified word, assuming the given POS.
     */
    
    public List getMeronyms(POS pos, String e) {
        Set meronymSet = new HashSet();
        if (pos == POS.ADJECTIVE || pos == POS.VERB)
            System.err.println("Sorry, adjectives and verbs don't have meronyms," +                " will return empty set");
        else {
            Word[] words;

            try {
                IndexWord word = d.lookupIndexWord(pos, e);
                if (word != null) {
                    //iterate over each sense of this word
                    for (int j = 1; j < word.getSenseCount() + 1; j++) {

                        Synset syn = word.getSense(j);

                        PointerTargetNodeList meronyms = PointerUtils.getMeronyms(syn);

                        if (meronyms != null) {
                            //if this sense of the word has meronyms, iterate over them
                            Iterator i = meronyms.iterator();
                            while (i.hasNext()) {

                                PointerTargetNode targetNode = (PointerTargetNode) i.next();
                                //if this meronym is just a string (lexical) 
                                //add it to the meronym list
                                if (targetNode.isLexical()) {
                                    meronymSet.add(targetNode.getWord().getLemma());

                                } else {
                                    // It's a synset rather than a word, 
                                    //so retrieve all the words and 
                                    //add them to the meronym list
                                    words = targetNode.getSynset().getWords();
                                    for (int t = 0; t < words.length; t++) {
                                        meronymSet.add(words[t].getLemma());

                                    }

                                }

                            }

                        }
                    }

                }

            } catch (JWNLException err) {
                err.printStackTrace();
            }
        }
        return new ArrayList(meronymSet);
    }

    /**
     * Retrieve a list of words representing the hyponyms for every sense of the
     *  specified word and POS.
     * Hyponym is the specific term used to designate a member of a class. 
     * X is a hyponym of Y if X is a (kind of) Y . 
     *
     * @param pos The part of speech for the word
     * 
     * @param e The word to be looked up 
     * 
     * @return A list containing all the direct hyponyms of all senses of the 
     * specified word, assuming the given POS.
     */
    
    public List getHyponyms(POS pos, String e) {
        Set hyponymSet = new HashSet();
        if (pos == POS.ADJECTIVE)
            System.err.println("Sorry, adjectives don't have hyponyms, " +                "will return empty set");
        else {
            Word[] words;

            try {
                IndexWord word = d.lookupIndexWord(pos, e);
                if (word != null) {
                    //iterate over each sense of this word
                    for (int j = 1; j < word.getSenseCount() + 1; j++) {

                        Synset syn = word.getSense(j);

                        PointerTargetNodeList hyponyms = PointerUtils.getDirectHyponyms(syn);

                        if (hyponyms != null) {
                            //if this sense of the word has hyponyms, iterate over them
                            Iterator i = hyponyms.iterator();
                            while (i.hasNext()) {

                                PointerTargetNode targetNode = (PointerTargetNode) i.next();
                                //if this hyponym is just a string (lexical) 
                                //add it to the hyponym list
                                if (targetNode.isLexical()) {
                                    hyponymSet.add(targetNode.getWord().getLemma());

                                } else {
                                    // It's a synset rather than a word, 
                                    //so retrieve all the words and add them to the hyponym list
                                    words = targetNode.getSynset().getWords();
                                    for (int t = 0; t < words.length; t++) {
                                        hyponymSet.add(words[t].getLemma());

                                    }

                                }

                            }

                        }
                    }

                }

            } catch (JWNLException err) {
                err.printStackTrace();
            }
        }
        return new ArrayList(hyponymSet);
    }

    /**
     * Look up all synonyms of this word, for all parts of speech
     * 
     * @param word The word for which synonyms are required
     * 
     * @return A list containing strings representing synonyms of the specified word
     */
    
    public List getAllSynonyms(String word) {

        Set answers = new HashSet();
        answers.addAll(getSynonyms(POS.VERB, word));
        answers.addAll(getSynonyms(POS.ADJECTIVE, word));
        answers.addAll(getSynonyms(POS.ADVERB, word));
        answers.addAll(getSynonyms(POS.NOUN, word));

        return new ArrayList(answers);

    }
    
    /**
     * Used to test the generation of random words from WordNet
     *
     */
    
    public void testRandomWords() {
        for (int i = 0; i < 100; i++) {

            System.out.println("Here is the verb " + getVerbAtRandom());
            System.out.println("Here is the adj " + getAdjectiveAtRandom());
            System.out.println("Here is the noun " + getNounAtRandom());
        }
    }
    
    /**
     * Used to test the code for generating synonyms for each part of speech
     *
     */
    
    public void testSynonyms() {
        for (int i = 0; i < 100; i++) {

            String noun = getNounAtRandom();
            String verb = getVerbAtRandom();
            String adj = getAdjectiveAtRandom();
            List nounsyns = getSynonyms(POS.NOUN, noun);
            Iterator it = nounsyns.iterator();
            System.out.println("\n Here are the synonyms for the randomly " +                "generated noun " + noun);
            while (it.hasNext()) {
                System.out.print((String) it.next() + ", ");
            }
            List verbsyns = getSynonyms(POS.VERB, verb);
            Iterator vit = verbsyns.iterator();
            System.out.println("\n Here are the synonyms for the randomly " +                "generated verb " + verb);
            while (vit.hasNext()) {
                System.out.print((String) vit.next() + ", ");
            }

            List adjsyns = getSynonyms(POS.ADJECTIVE, adj);
            Iterator ait = adjsyns.iterator();
            System.out.println("\n Here are the synonyms for the randomly " +                "generated adjective " + adj);
            while (ait.hasNext()) {
                System.out.print((String) ait.next() + ", ");
            }
        }

    }

    /**
     * Used to test the code for generating synonyms for nouns and verbs
     *
     */
    
    public void testHyponyms() {
        for (int i = 0; i < 100; i++) {

            String noun = getNounAtRandom();
            String verb = getVerbAtRandom();

            List nounsyns = getHyponyms(POS.NOUN, noun);
            Iterator it = nounsyns.iterator();
            System.out.println("\n Here are the hyponyms for the randomly " +                "generated noun " + noun);
            while (it.hasNext()) {
                System.out.print((String) it.next() + ", ");
            }
            List verbsyns = getHyponyms(POS.VERB, verb);
            Iterator vit = verbsyns.iterator();
            System.out.println("\n Here are the hyponyms for the randomly " +                "generated verb " + verb);
            while (vit.hasNext()) {
                System.out.print((String) vit.next() + ", ");
            }

        }

    }
    
    /**
     * Used to test the code for generating hypernyms for nouns and verbs
     *
     */

    public void testHypernyms() {
        for (int i = 0; i < 100; i++) {

            String noun = getNounAtRandom();
            String verb = getVerbAtRandom();

            List nounsyns = getHypernyms(POS.NOUN, noun);
            Iterator it = nounsyns.iterator();
            System.out.println("\n Here are the hypernyms for the randomly " +                "generated noun " + noun);
            while (it.hasNext()) {
                System.out.print((String) it.next() + ", ");
            }
            List verbsyns = getHypernyms(POS.VERB, verb);
            Iterator vit = verbsyns.iterator();
            System.out.println("\n Here are the hypernyms for the randomly " +                "generated verb " + verb);
            while (vit.hasNext()) {
                System.out.print((String) vit.next() + ", ");
            }

        }

    }

    /**
     * Used to test the code for generating meronyms for nouns 
     *
     */
    public void testMeronyms() {
        for (int i = 0; i < 100; i++) {

            String noun = getNounAtRandom();
            String verb = getVerbAtRandom();

           List nounsyns = getMeronyms(POS.NOUN, noun);
            Iterator it = nounsyns.iterator();
            System.out.println("\n Here are the meronyms for the randomly " +                "generated noun " + noun);
            while (it.hasNext()) {
                System.out.print((String) it.next() + ", ");
            }

        }

    }

    /**
     * Creates a new interface to WordNet and tests methods
     * @param args None necessary
     */
    public static void main(String args[]) {

        WordNetWrapper w = new WordNetWrapper();
        //w.testRandomWords();
        w.testSynonyms();
        //w.testHypernyms();
        //w.testHyponyms();
        //w.testMeronyms();

    }

}
